﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets.Encryptions;

namespace ConquerServer_Basic
{
    public class PacketProcessor
    {
        public static void AcceptInviteToJoinTeam(GameClient Client, TeamPacket Packet)
        {
            if (Client.Team == null && !Client.Entity.Dead)
            {
                GameClient Leader; // Team leader
                if (Kernel.GamePool.TryGetValue(Packet.PlayerUID, out Leader))
                {
                    if (Leader.Team != null)
                    {
                        if (Leader.Team.Full)
                            return;

                        Client.Team = new PlayerTeam();

                        AddToTeamPacket AddYou = new AddToTeamPacket();
                        AddToTeamPacket AddMe = new AddToTeamPacket();
                        AddMe.MaxHitpoints = (ushort)Client.Entity.MaxHitpoints;
                        AddMe.Hitpoints = (ushort)Client.Entity.Hitpoints;
                        AddMe.Model = (ushort)Client.Entity.Model;
                        AddMe.UID = Client.Entity.UID;
                        AddMe.Name = Client.Entity.Name;

                        foreach (GameClient Teammate in Leader.Team.Teammates)
                        {
                            if (Teammate != null)
                            {
                                Teammate.Send(AddMe);
                                Client.Team.Add(Teammate);
                                AddYou.MaxHitpoints = (ushort)Teammate.Entity.MaxHitpoints;
                                AddYou.Hitpoints = (ushort)Teammate.Entity.Hitpoints;
                                AddYou.Model = Teammate.Entity.Model;
                                AddYou.UID = Teammate.Identifier;
                                AddYou.Name = Teammate.Entity.Name;
                                Client.Send(AddYou);
                                if (Teammate.Identifier != Leader.Identifier)
                                    Teammate.Team.Add(Client);
                            }
                        }   // |                    ^
                        //     v  Makes up for that | 
                        Leader.Team.Add(Client);
                        Client.Team.Add(Client);
                        Client.Team.Active = true;
                        Client.Team.TeamLeader = false;
                        Client.Send(AddMe);
                    }
                }
            }
        }
        public static void SendInviteToJoinTeam(GameClient Client, TeamPacket Packet)
        {
            if (Client.Team != null)
            {
                if (!Client.Team.Full && Client.Team.TeamLeader)
                {
                    GameClient Invitee; // Person your inviting
                    if (Kernel.GamePool.TryGetValue(Packet.PlayerUID, out Invitee))
                    {
                        if (Invitee.Team == null)
                        {
                            Packet.PlayerUID = Client.Identifier;
                            Invitee.Send(Packet);
                        }
                        else
                        {
                            Client.Send(new MessagePacket(Invitee.Entity.Name + " is already in a team.", 0x00FF0000, MessagePacket.TopLeft));
                        }
                    }
                }
            }
        }
        public static void AcceptRequestToJoinTeam(GameClient Client, TeamPacket Packet)
        {
            if (Client.Team != null && !Client.Entity.Dead)
            {
                if (!Client.Team.Full && Client.Team.TeamLeader)
                {
                    GameClient NewTeammate;
                    if (Kernel.GamePool.TryGetValue(Packet.PlayerUID, out NewTeammate))
                    {
                        if (NewTeammate.Team != null)
                            return;

                        NewTeammate.Team = new PlayerTeam();

                        AddToTeamPacket AddMe = new AddToTeamPacket();
                        AddToTeamPacket AddYou = new AddToTeamPacket();
                        AddYou.MaxHitpoints = (ushort)NewTeammate.Entity.MaxHitpoints;
                        AddYou.Hitpoints = (ushort)NewTeammate.Entity.Hitpoints;
                        AddYou.Model = NewTeammate.Entity.Model;
                        AddYou.UID = NewTeammate.Entity.UID;
                        AddYou.Name = NewTeammate.Entity.Name;

                        foreach (GameClient Teammate in Client.Team.Teammates)
                        {
                            if (Teammate != null)
                            {
                                Teammate.Send(AddYou);
                                NewTeammate.Team.Add(Teammate);
                                AddMe.MaxHitpoints = (ushort)Teammate.Entity.MaxHitpoints;
                                AddMe.Hitpoints = (ushort)Teammate.Entity.Hitpoints;
                                AddMe.Model = Teammate.Entity.Model;
                                AddMe.UID = Teammate.Identifier;
                                AddMe.Name = Teammate.Entity.Name;
                                NewTeammate.Send(AddMe);
                                // Prevents System.InvalidOperationException
                                if (Teammate.Identifier != Client.Identifier)
                                    Teammate.Team.Add(NewTeammate);
                            }   // |                    ^
                        }       // v  Makes up for that | 
                        Client.Team.Add(NewTeammate);
                        NewTeammate.Team.Add(NewTeammate);
                        NewTeammate.Team.Active = true;
                        NewTeammate.Team.TeamLeader = false;
                        NewTeammate.Send(AddYou);
                    }
                }
            }
        }
        public static void SendRequestJoinToTeam(GameClient Client, TeamPacket Packet)
        {
            if (Client.Team == null && !Client.Entity.Dead)
            {
                GameClient Leader;
                if (Kernel.GamePool.TryGetValue(Packet.PlayerUID, out Leader))
                {
                    if (Leader.Team != null)
                    {
                        if (Leader.Team.TeamLeader && !Leader.Team.Full)
                        {
                            Packet.PlayerUID = Client.Identifier;
                            Leader.Send(Packet);
                        }
                        else
                        {
                            Client.Send(new MessagePacket(Leader.Entity.Name + "'s team is already full.", 0x00FF0000, MessagePacket.TopLeft));
                        }
                    }
                    else
                    {
                        Client.Send(new MessagePacket(Leader.Entity.Name + "'s doesn't have a team.", 0x00FF0000, MessagePacket.TopLeft));
                    }
                }
            }
        }
        public static void LeaveTeam(GameClient Client, TeamPacket Packet)
        {
            if (Client.Team != null)
            {
                if (!Client.Team.TeamLeader)
                {
                    foreach (GameClient Teammate in Client.Team.Teammates)
                    {
                        if (Teammate != null)
                        {
                            if (Teammate.Identifier != Client.Identifier)
                            {
                                Teammate.Send(Packet);
                                Teammate.Team.Remove(Client.Identifier);
                            }
                        }
                    }
                    Client.Send(Packet);
                    Client.Team = null;
                }
            }
        }
        public static void KickFromTeam(GameClient Client, TeamPacket Packet)
        {
            if (Client.Team != null)
            {
                if (Client.Team.TeamLeader)
                {
                    GameClient Teammate; // The guy we're kicking out
                    if (Kernel.GamePool.TryGetValue(Packet.PlayerUID, out Teammate))
                    {
                        if (Teammate.Team != null)
                        {
                            if (Teammate.Team.IsTeammate(Client.Identifier))
                            {
                                LeaveTeam(Teammate, Packet);
                            }
                        }
                    }
                }
            }
        }
        public static void DismissTeam(GameClient Client, TeamPacket Packet)
        {
            if (Client.Team != null)
            {
                if (!Client.Entity.Dead && Client.Team.TeamLeader)
                {
                    foreach (GameClient Teammate in Client.Team.Teammates)
                    {
                        if (Teammate != null)
                        {
                            if (Teammate.Identifier != Client.Identifier)
                            {
                                Teammate.Send(Packet);
                                Teammate.Team = null;
                            }
                        }
                    }
                    Client.Send(Packet);
                    Client.Team = null;

                    Client.Entity.StatusFlag &= ~SyncPacket.Flags.TeamLeader;

                    SyncPacket status = new SyncPacket(1);
                    status.UID = Client.Identifier;
                    status[0] = SyncPacket.Data.Create(SyncPacket.RaiseFlag, Client.Entity.StatusFlag);
                    Client.SendScreen(status, true);
                }
            }
        }
        public static void CreateTeam(GameClient Client, TeamPacket Packet)
        {
            if (!Client.Entity.Dead && Client.Team == null)
            {
                Client.Team = new PlayerTeam();
                Client.Team.Active = true;
                Client.Team.TeamLeader = true;
                Client.Team.Add(Client);
                Client.Send(Packet);

                Client.Entity.StatusFlag |= SyncPacket.Flags.TeamLeader;
                SyncPacket status = new SyncPacket(1);
                status.UID = Client.Identifier;
                status[0] = SyncPacket.Data.Create(SyncPacket.RaiseFlag, Client.Entity.StatusFlag);
                Client.SendScreen(status, true);
            }
        }

        public static void EquipGear(GameClient Client, ItemUsagePacket Packet)
        {
            IConquerItem EquipItem = Client.GetInventoryItem(Packet.UID);
            if (EquipItem != null)
            {
                Client.RemoveInventory(EquipItem.UID);

                ushort EquipSlot = (ushort)Packet.dwParam;
                if (Client.Equipment.ContainsKey(EquipSlot))
                {
                    if (!Client.Unequip(EquipSlot))
                        throw new Exception("PacketProcessor::EquipGear() -> Failed to call Client.Unequip()");
                }
                if (!Client.Equip(EquipItem, EquipSlot))
                    throw new Exception("PacketProcessor::EquipGear() -> Failed to call Client.Equip()");

                Client.CalculateStatBonus();
                Client.CalculateHPBonus();
                Client.CalculateDamageBoost();
                Client.SendStatMessage();
            }
        }
        public static void UnequipGear(GameClient Client, ItemUsagePacket Packet)
        {
            ushort EquipSlot = (ushort)Packet.dwParam;
            if (Client.Equipment.ContainsKey(EquipSlot))
            {
                if (!Client.Unequip(EquipSlot))
                    throw new Exception("PacketProcessor::UnequipGear() -> Failed to call Client.Unequip()");

                Client.CalculateStatBonus();
                Client.CalculateHPBonus();
                Client.CalculateDamageBoost();
                Client.SendStatMessage();
            }
        }

        public static void ChangePKMode(GameClient Client, DataPacket Packet)
        {
            Client.PKMode = (PKMode)(Packet.dwParam);
            Client.SendScreen(Packet, false);
        }
        public static void ChangeAction(GameClient Client, DataPacket Packet)
        {
            Client.Entity.Action = (ConquerAction)(Packet.dwParam);
            Client.SendScreen(Packet, false);
        }      
        public static void ChangeDirection(GameClient Client, DataPacket Packet)
        {
            Client.Entity.Facing = (ConquerAngle)(Packet.wParam3 % 8);
            Client.SendScreen(Packet, false);
        }

        public static void HandleNpcRequest(GameClient Client, NpcRequestPacket Packet, bool First)
        {
            if (First)
                Client.ActiveNpcID = Packet.NpcID;
            NpcProcessor.Process(Client, Packet.OptionID, Packet.Input);
        }

        public static void PlayerGroundMovment(GameClient Client, GroundMovementPacket Packet)
        {
            Client.SendScreen(Packet, true);
            Client.Entity.Move(Packet.Direction);
            Client.Screen.Reload(false, null);
        }
        public static void PlayerJump(GameClient Client, DataPacket Packet)
        {
            ushort new_X = (ushort)(Packet.dwParam & 0xFFFF);
            ushort new_Y = (ushort)(Packet.dwParam >> 16);

            if (Kernel.GetDistance(new_X, new_Y, Client.Entity.X, Client.Entity.Y) <= 16)
            {
                Client.Entity.Action = ConquerAction.Jump;
                Client.SendScreen(Packet, true);

                Client.Entity.Facing = (ConquerAngle)Packet.wParam3;
                Client.Entity.X = new_X;
                Client.Entity.Y = new_Y;

                Client.Screen.Reload(false, null);
            }
            else
            {
                throw new Exception("PacketProcessor::PlayerJump() -> Failed To Assert `Kernel.GetDistance(new_X, new_Y, ClientX, ClientY) <= 16`");
            }
        }

        public static void CompleteLogin(GameClient Client)
        {
            Client.LoadEquipment();
            Client.CalculateStatBonus();
            Client.CalculateHPBonus();
            Client.CalculateDamageBoost();

            Client.Stamina = 100;
            //Client.Entity.Hitpoints = Math.Min(Client.Entity.Hitpoints, Client.Entity.MaxHitpoints);
            Client.Entity.Hitpoints = Client.Entity.MaxHitpoints;
            // ^ temp because reviving isn't added if someones dead, lol
            
            if (Client.Entity.Dead)
            {
                // revive
            }
            else
            {
                SyncPacket sync = new SyncPacket(4);
                sync.UID = Client.Identifier;
                sync.HitpointsAndMana(Client, 0);
                sync[2] = SyncPacket.Data.Create(SyncPacket.Stamina, Client.Stamina);
                sync[3] = SyncPacket.Data.Create(SyncPacket.RaiseFlag, Client.Entity.StatusFlag);
                Client.Send(sync);
            }
            Client.AuthPhase = AuthPhases.FullLogin;
        }
        public static void AppendLocation(GameClient Client, DataPacket Packet)
        {
            Packet.UID = Client.Entity.UID;
            Packet.dwParam = Client.Entity.MapID;
            Packet.wParam1 = Client.Entity.X;
            Packet.wParam2 = Client.Entity.Y;
            Client.Send(Packet);
        }
        public static void GetSurroundings(GameClient Client)
        {
            // Spawn people who I can see to me,
            // and spawn me to them also (callback)
            Client.Screen.Reload(true,
                    delegate(IBaseEntity Sender, IBaseEntity Caller)
                    {
                        if (Caller.EntityFlag == EntityFlag.Player &&
                            Sender.EntityFlag == EntityFlag.Player)
                        {
                            GameClient __Client = Caller.Owner as GameClient;
                            __Client.Send(Client.Entity.SpawnPacket);
                        }
                        return 0;
                    }
                );
        }
        public static void GameConnect(GameClient Client, byte[] Packet)
        {
            Client.Identifier = BitConverter.ToUInt32(Packet, 8);
            uint Key2 = BitConverter.ToUInt32(Packet, 4);
            (Client.Socket.Crypto as ConquerStanderedCipher).SetKeys(Client.Identifier, Key2);
            AuthClient authData;
            if (Kernel.AuthPool.TryGetValue(Client.Identifier, out authData))
            {
                Client.Username = authData.Username;
                Client.Password = authData.Password;
                Kernel.AuthPool.Remove(authData.Identifier);

                if (Database.LoadCharacter(Client))
                {
                    Kernel.GamePool.ThreadSafeAdd<uint, GameClient>(Client.Identifier, Client);
                    Kernel.UpdateGameClients();

                    Client.Send(PacketBuilder.CharacterInfo(Client));
                    Client.Send(new MessagePacket("ANSWER_OK", "ALLUSERS", 0xFFFFFF, MessagePacket.Dialog));
                    return;
                }
            }
            Client.Socket.Disconnect();
        }

        public static void Process(GameClient Client, byte[] Packet, ushort Type)
        {
            try
            {
                switch (Type)
                {
                    case 1004:
                        {
                            MessagePacket cPacket = new MessagePacket();
                            cPacket.Deserialize(Packet);

                            if (!cPacket.Message.StartsWith("@"))
                            {
                                switch (cPacket.ChatType)
                                {
                                    default:
                                        Client.SendScreen(Packet, false);
                                        break;
                                }
                            }
                            else
                            {
                                ConquerCommand.Parse(Client, cPacket._From, cPacket._To, cPacket.Message);
                            }
                            break;
                        }

                    case 1005:
                        {
                            GroundMovementPacket cPacket = new GroundMovementPacket(false);
                            cPacket.Deserialize(Packet);
                            PlayerGroundMovment(Client, cPacket);
                            break;
                        }

                    case 1052:
                        {
                            GameConnect(Client, Packet);
                            break;
                        }

                    case 1009:
                        {
                            ItemUsagePacket cPacket = new ItemUsagePacket(false);
                            cPacket.Deserialize(Packet);
                            switch (cPacket.ID)
                            {
                                case ItemUsagePacket.Ping:
                                    Client.Send(Packet); // Just echo it back.
                                    break;
                                case ItemUsagePacket.UnequipItem:
                                    UnequipGear(Client, cPacket);
                                    break;
                                case ItemUsagePacket.EquipItem:
                                    EquipGear(Client, cPacket);
                                    break;
                            }
                            break;
                        }

                    case 1010:
                        {
                            DataPacket cPacket = new DataPacket(false);
                            cPacket.Deserialize(Packet);
                            switch (cPacket.ID)
                            {
                                case DataPacket.SetLocation:
                                    AppendLocation(Client, cPacket);
                                    break;

                                case DataPacket.ChangeDirection:
                                    ChangeDirection(Client, cPacket);
                                    break;

                                case DataPacket.GetSurroundings:
                                    GetSurroundings(Client);
                                    break;

                                case DataPacket.ChangeAction:
                                    ChangeAction(Client, cPacket);
                                    break;

                                case DataPacket.Hotkeys:
                                    if (Client.AuthPhase == AuthPhases.GameServer)
                                    {
                                        Client.AuthPhase = AuthPhases.GameComplete;
                                        foreach (IConquerItem Item in Client.Inventory)
                                        {
                                            Item.Send(Client);
                                        }
                                        Client.Send(Packet);
                                    }
                                    break;

                                case DataPacket.ConfirmFriends: // ' Not implemented yet, echo when its done
                                    Client.Send(Packet);
                                    break;

                                case DataPacket.ConfirmSpells: // ' Not implemented, echo when its done
                                    Client.Send(Packet);
                                    break;

                                case DataPacket.ConfirmProfincies: // ' Not implemented, echo when its done
                                    Client.Send(Packet);
                                    break;

                                case DataPacket.ConfirmGuild: // ' Not implemented, echo when its done
                                    Client.Send(Packet);
                                    break;

                                case DataPacket.CompleteLogin:
                                    CompleteLogin(Client);
                                    break;

                                case DataPacket.Jump:
                                    PlayerJump(Client, cPacket);
                                    break;
                            }
                            break;
                        }

                    case 2031:
                    case 2032:
                        {
                            NpcRequestPacket cPacket = new NpcRequestPacket();
                            cPacket.Deserialize(Packet);
                            if (cPacket.OptionID != NpcRequestPacket.BreakOnCase)
                            {
                                HandleNpcRequest(Client, cPacket, (Type == 2031));
                            }
                            break;
                        }
                    case 1023:
                        {
                            TeamPacket cPacket = new TeamPacket(false);
                            cPacket.Deserialize(Packet);
                            switch (cPacket.ID)
                            {
                                case TeamPacket.Create: CreateTeam(Client, cPacket); break;
                                case TeamPacket.AcceptJoinRequest: AcceptRequestToJoinTeam(Client, cPacket); break;
                                case TeamPacket.AcceptInvitation: AcceptInviteToJoinTeam(Client, cPacket); break;
                                case TeamPacket.InviteRequest: SendInviteToJoinTeam(Client, cPacket); break;
                                case TeamPacket.JoinRequest: SendRequestJoinToTeam(Client, cPacket); break;
                                case TeamPacket.ExitTeam: LeaveTeam(Client, cPacket); break;
                                case TeamPacket.Dismiss: DismissTeam(Client, cPacket); break;
                                case TeamPacket.Kick: KickFromTeam(Client, cPacket); break;
                            }
                            break;
                        }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("[PacketProcessor -> Dropped Client]\r\nReason:\r\n" + e.ToString());
                Client.Socket.Disconnect();
            }
        }
    }
}
